/*
  Meanwhile - Unofficial Lotus Sametime client library
  
  Copyright (c) 2007 Christopher O'Brien <siege@preoccupied.net>
  
  License: LGPL
  
  This library is free software; you can redistribute it and/or modify
  it under the terms of version 2 the GNU Library General Public
  License as published by the Free Software Foundation.
                                                                                
  This library is distributed in the hope that it will be useful, but
  WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
  Library General Public License for more details.
  
  You should have received a copy of the GNU Library General Public
  License along with this library; if not, write to the Free Software
  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
  USA
*/


#include "mw-common.h"
#include <string.h>


struct mw_read_buffer {
  const guchar *buf;  /**< buffer */
  gsize len;    /**< total bytes */

  guchar *ptr;  /**< current position */
  gsize rem;    /**< remaining bytes */

  /** TRUE if the read buffer was created via
      mw_read_buffer_new_wrap */
  gboolean wrap;
};


MwReadBuffer *mw_read_buffer_new_copy(const gpointer data, gsize len) {
  MwReadBuffer *b = g_new0(MwReadBuffer, 1);
  
  if(data && len) {
    b->buf = b->ptr = g_memdup(data, len);
    b->len = b->rem = len;
  }

  return b;
}


MwReadBuffer *mw_read_buffer_new_wrap(const gpointer data, gsize len) {
  MwReadBuffer *b = g_new0(MwReadBuffer, 1);

  if(data && len) {
    b->buf = b->ptr = (gpointer) data;
    b->len = b->rem = len;
  }
  b->wrap = TRUE;

  return b;
}


void mw_read_buffer_free(MwReadBuffer *mwrb) {
  if(mwrb) {
    if(! mwrb->wrap) {
      g_free(mwrb->buf);
    }
    g_free(mwrb);
  }
}


gsize mw_read_buffer_get_remaining(MwReadbuffer *mwrb) {
  g_return_val_if_fail(mwrb != NULL, 0);
  return mwrb->rem;
}


gsize mw_read_buffer_read(MwReadBuffer *mwrb, gpointer mem, gsize len) {
  g_return_val_if_fail(mwrb != NULL, 0);
  g_return_val_if_fail(mem != NULL, 0);

  if(len == 0) {
    return 0;
  }

  if(mwrb->rem < len) {
    len = mwrb->rem;
  }

  memcpy(data, mwrb->ptr, len);
  mwrb->ptr += len;
  mwrb->rem -= len;

  return len;
}


gsize mw_read_buffer_advance(MwReadBuffer *mwrb, gsize len) {
  g_return_val_if_fail(mwrb != NULL, 0);

  if(len == 0) {
    return 0;
  }

  if(mwrb->rem < len) {
    len = mwrb->rem;
  }

  mwrb->ptr += len;
  mwrb->rem -= len;

  return len;
}


void mw_read_buffer_reset(MwReadBuffer *mwrb) {
  g_return_if_fail(mwrb != NULL);

  mwrb->rem = mwrb->len;
  mwrb->ptr = mwrb->buf;
}


void mw_read_buffer_unpack(MwReadBuffer *mwrb, GError **err,
			   const gchar *defn, ...) {
  va_list vargs;
  va_start(vargs, defn);
  mw_read_buffer_unpackv(mwrb, err, defn, vargs);
  va_end(vargs);
}


static void mw_unpack_error(GError *src, const gchar *str, guint index) {
  gchar *orig_msg, *new_msg;

  g_return_if_fail(src != NULL);
  g_return_if_fail(str != NULL);

  orig_msg = src->message;
  new_msg = g_strdup_printf("error unpacking \"%s\", index %u: '%c'\n%s",
			    str, index, str+index, NSTR(orig_msg));
  
  src->code = MW_ERROR_READ_UNPACK;
  src->message = new_msg;
  g_free(orig_msg);
}


void mw_read_buffer_unpackv(MwReadBuffer *mwrb, GError **err,
			    const gchar *defn, va_list vargs) {

  GError *e = NULL;
  gchar *d;
  guint i = 0;

  g_return_if_fail(mwrb != NULL);
  g_return_if_fail(defn != NULL);
  g_return_if_fail(err == NULL || *err == NULL);

  for(d = (gchar *) defn; *d; d++, i++) {
    gpointer val = va_arg(vargs, gpointer);

    switch(*d) {
    case 'b':
      mw_gboolean_read(mwrb, val, &e);
      break;

    case 'u':
      mw_guint16_read(mwrb, val, &e);
      break;

    case 'U':
      mw_guint32_read(mwrb, val, &e);
      break;

    case 's':
      mw_str_read(mwrb, val, &e);
      break;

    case 'o':
      mw_opaque_read(mwrb, val, &e);
      break;

    case 'z': {
      mw_unpack_func func = va_arg(vargs, mw_unpack_func);
      func(mwrb, val, &e);
      break;
    }

    default:
      /* this is a programming problem, not to be caught with GError */
      g_warning("Unrecognized unpack character '%c' in \"%s\"", *d, defn);
      return;
    }
    
    /* if there was an error attempting to unpack something, then stop
       the unpacking process, wrap the error to indicate where in the
       unpacking process it occurred, and propagate it upwards */
    if(e) {
      mw_wrap_unpack_error(e, defn, i);
      g_propagate_error(err, e);
      return;
    }
  }
}


void mw_read_buffer_unpack_items(MwReadBuffer *mwrb, GError **err,
				 gsize item_size,
				 guint count, gpointer *items,
				 mw_unpack_func unpack) {
  guint i;
  gpointer mem = NULL;
  
  g_return_if_fail(mwrb != NULL);
  g_return_if_fail(err == NULL || *err == NULL);
  g_return_if_fail(items != NULL);
  g_return_if_fail(unpack != NULL);

  if(count) {
    /* allocate count contiguos items */
    mem = g_malloc0(item_size * count);
  }

  /* unpack into each item */
  for(i = 0; i < count; i++) {
    unpack(mwrb, mem+(i*item_size), err);
    if(err && *err) {
      break;
    }
  }

  *items = mem;
}



struct mw_write_buffer {
  guchar *buf;
  gsize len;
  
  guchar *ptr;
  gsize rem;
};


static gsize buffer_used(MwWriteBuffer *b) {
  return (b->len - b->rem);
}


static void ensure_buffer(MwWriteBuffer *b, gsize needed) {
  g_return_if_fail(b != NULL);

  if(b->rem < needed) {
    gsize len, use;

    len = b->len;
    use = buffer_used(b);

    if(! len) {
      len = 1024;
    }

    while((len - use) < needed) {
      len *= 2;
    }

    b->buf = g_realloc(b->buf, len);
    b->len = len;
    b->ptr = buf + use;
    b->rem = len - use;
  }
}


MwWriteBuffer *mw_write_buffer_new() {
  return mw_write_buffer_new_sized(0);
}


MwWriteBuffer *mw_write_buffer_new_sized(gsize buflen) {
  MwWriteBuffer *b = g_new0(MwWriteBuffer, 1);
  ensure_buffer(b, buflen);
  return b;
}


void mw_write_buffer_finalize(MwWriteBuffer *writeb,
			      gpointer *data, gsize *len) {

  g_return_if_fail(writeb != NULL);
  g_return_if_fail(data != NULL);
  g_return_if_fail(len != NULL);
  
  *data = writeb->buf;
  *len = buffer_used(writeb);

  g_free(writeb);
}


void mw_write_buffer_free(MwWriteBuffer *writeb) {
  if(writeb) {
    g_free(writeb->buf);
    g_free(writeb);
  }
}


void mw_write_buffer_write(MwWriteBuffer *writeb, gpointer data, gsize len) {
  g_return_if_fail(writeb != NULL);
  g_return_if_fail(data != NULL);

  if(len) {
    ensure_buffer(writeb, len);
    memcpy(writeb->ptr, data, len);
    writeb->ptr += len;
    writeb->rem -= len;
  }
}


void mw_write_buffer_pack(MwWriteBuffer *writeb,
			  const gchar *defn, ...) {
  va_list vargs;
  va_start(vargs, defn);
  mw_write_buffer_packv(writeb, defn, vargs);
  va_end(vargs);
}


void mw_write_buffer_packv(MwWriteBuffer *writeb,
			   const gchar *defn, va_list vargs) {
  
  gchar *d;

  g_return_if_fail(mwrb != NULL);
  g_return_if_fail(defn != NULL);

  for(d = (gchar *) defn; *d; d++) {
    switch(*d) {
    case 'b': {
      gboolean val = va_arg(vargs, gboolean);
      mw_gboolean_write(mwrb, val);
      break;
    }

    case 'u': {
      guint16 val = va_arg(vargs, guint16);
      mw_guint16_write(mwrb, val);
      break;
    }

    case 'U': {
      guint32 val = va_arg(vargs, guint32);
      mw_guint32_write(mwrb, val);
      break;
    }

    case 's': {
      gchar *val = va_arg(vargs, (gchar *));
      mw_str_write(mwrb, val);
      break;
    }

    case 'o': {
      MwOpaque *val = va_arg(vargs, (MwOpaque *));
      mw_opaque_write(mwrb, val);
      break;
    }

    case 'z': {
      gpointer val = va_args(vargs, gpointer);
      mw_pack_func func = va_arg(vargs, mw_unpack_func);
      func(mwrb, val);
      break;
    }

    default:
      mw_warning("Unrecognized pack character '%c' in \"%s\"", *d, defn);
      return;
    }
  }
}


void mw_write_buffer_pack_items(MwWriteBuffer *mwb,
				gsize item_size,
				guint count, gpointer items,
				mw_pack_func pack) {
  guint i;

  g_return_if_fail(mwb != NULL);
  g_return_if_fail(items != NULL || count == 0);
  g_return_if_fail(pack != NULL);

  for(i = 0; i < count; i++) {
    pack(mwb, items+(item_size*count));
  }
}



void mw_copy_items(gpointer *to,
		   guint count, gsize item_size, const gpointer items,
		   mw_copy_func copyf) {
  
  gpointer n;
  
  g_return_if_fail(to != NULL);
  g_return_if_fail(*to == NULL);
  g_return_if_fail(items != NULL);
  g_return_if_fail(copyf != NULL);

  n = g_malloc0(count * item_size);

  while(count--) {
    copyf(n+(count*item_size), items+(count*item_size));
  }

  *to = n;
}


gpointer mw_dup_items(guint count, gsize item_size, const gpointer items,
		      mw_copy_func copyf) {
  gpointer n = NULL;
  mw_copy_items(&n, to->desc = g_strdup(from->desc));
  return n;
}


void mw_clear_items(guint count, gsize item_size, gpointer items,
		    mw_clear_func clearf) {

  g_return_if_fail(count == 0 || items != NULL);
  g_return_if_fail(clearf != NULL);

  while(count--) {
    clearf(items+(count*item_size));
  }
}


void mw_free_items(guint count, gsize item_size, gpointer items,
		   mw_clear_func clearf) {

  g_return_if_fail(count == 0 || items != NULL);
  g_return_if_fail(clearf != NULL);
  
  mw_clear_items(count, item_size, items, clearf);
  g_free(items);
}



GType mw_status_get_type() {
  static GType type = 0;
  if(! type) {
    type = g_boxed_type_register_static("MwStatus",
					GBoxedCopyFunc(mw_status_dup),
					GBoxedFreeFunc(mw_status_free));
  }
  return type;
}


MwStatus mw_status_new() {
  return g_new0(MwStatus, 1);
}


MwStatus *mw_status_dup(const MwStatus *status) {
  MwStatus *stat = mw_status_new();
  mw_status_copy(stat, status);
  return stat;
}


void mw_status_copy(MwStatus *a, const MwStatus *b) {
  g_return_if_fail(a != NULL);
  g_return_if_fail(b != NULL);

  mw_status_clear(a);
  
  a->status = b->status;
  a->time = b->time;
  a->desc = g_strdup(b->desc);
}


void mw_status_read(MwReadBuffer *mwb, MwStatus *status, GError **err) {
  MW_TRYLINE(err, mw_read_buffer_unpack(mwb, err, "uUs",
					&status->status, &status->time,
					&status->desc));
}


void mw_status_write(MwWriteBuffer *mwb, const MwStatus *status) {
  mw_write_buffer_pack(mwb, "uUs", status->status, status->time,
		       status->desc);
}


void mw_status_clear(MwStatus *status) {
  g_return_if_fail(status != NULL);
  
  status->status = 0;
  status->time = 0;

  g_free(status->desc);
  status->desc = NULL;
}


void mw_status_free(MwStatus *status) {
  mw_status_clear(status);
  g_free(status);
}



GType mw_privacy_get_type() {
  static GType type = 0;
  if(! type) {
    type = g_boxed_type_register_static("MwPrivacy",
					GBoxedCopyFunc(mw_privacy_dup),
					GBoxedFreeFunc(mw_privacy_free));
  }
  return type;
}


MwPrivacy *mw_privacy_new() {
  return g_new0(MwPrivacy, 1);
}


MwPrivacy *mw_privacy_dup(const MwPrivacy *privacy) {
  MwPrivacy *new_privacy;
  
  g_return_val_if_fail(privacy != NULL);

  new_privacy = mw_privacy_new();
  mw_privacy_copy(new_privacy, privacy);
  return new_privacy;
}


void mw_privacy_copy(MwPrivacy *to, const MwPrivacy *from) {
  g_return_if_fail(to != NULL);
  g_return_if_fail(from != NULL);

  mw_privacy_clear(to);

  to->deny = from->deny;
  to->count = from->count;
  to->users = MW_DUP_ITEMS(from->count, MwIdentity, from->users,
			   mw_identity_copy);
}


void mw_privacy_read(MwReadBuffer *mwrb, MwPrivacy *privacy, GError **err) {
  MW_TRYLINE(err, mw_read_buffer_unpack(mwrb, err, "bU",
					&privacy->deny,
					&privacy->count));

  MW_TRYLINE(MW_UNPACK_ITEMS(mwrb, err, MwIdentity, privacy->count,
			     &privacy->users, mw_identity_read));
}


void mw_privacy_write(MwWriteBuffer *mwwb, const MwPrivacy *privacy) {
  mw_write_buffer_pack(mwwb, "bU", privacy->deny, privacy->count);
  MW_PACK_ITEMS(mwwb, MwIdentity, privacy->count, privacy->users,
		mw_identity_write);
}


void mw_privacy_clear(MwPrivacy *privacy) {
  guint c;
  
  g_return_if_fail(privacy != NULL);
  
  privacy->deny = FALSE;
  
  c = privacy->count;
  privacy->count = 0;
  
  MW_FREE_ITEMS(c, MwIdentity, privacy->users, mw_identity_clear);
  privacy->users = NULL;
}


void mw_privacy_free(MwPrivacy *privacy) {
  mw_privacy_clear(privacy);
  g_free(privacy);
}



GQuark mw_error_quark() {
  static error_quark = (GQuark) 0;
  if(! error_quark) {
    error_quark = g_quark_from_string(MW_LOG_DOMAIN);
  }
  return error_quark;
}

 
void mw_wrap_error(GError *err, const gchar *frmt, ...) {
  va_list vargs;
  va_start(vargs, frmt);
  mw_wrap_errorv(err, frmt, vargs);
  va_end(vargs);
}


void mw_wrap_errorv(GError *err, const gchar *frmt, va_list vargs) {
  if(err) {
    gchar *msg, *orig_msg = NULL;

    msg = g_strdup_vprintf(frmt, vargs);
    orig_msg = err->message;
    
    err->message = g_strdup_printf("%s\n%s", msg, NSTR(orig_msg));
    
    g_free(msg);
    g_free(orig_msg);
  }
}


void mw_line_error(GError *err,
		   const gchar *file, const gchar *func, gint line) {

  if(err) {
    mw_wrap_error(err, "error from %s:%s:%u", NSTR(file), NSTR(func), line);
  }
}


/* The end. */
